4 Flex とのインターフェイス


この章では C および Bison と一緒に Flex を使う方法を説明します。C、Bison のそれぞれが非常に多くの細目を含むため、本章は2つの部分に分割されています。それぞれの章において、全般的なインターフェイス概念に関する節と実例を示す節が提供されます。

4.1 Flex と C

Flex に対する C の主要なインターフェイスは、以下に挙げるルーチンと変数によるものです。以下の節を読む際には、いくつかの細かな部分で Flex と Lex との間に相違点があるということを意識しておいてください。Lex が提供していない関数がいくつかありますし、宣言の内容が違うものもあります。これらは通常大きな問題にはなりません。というのは、相違のある関数は一般的にはあまり使われていないからです。相違点に関する詳細については、***ページの 8 章「Flex と Lex」および***ページの 8.1.1 節「Flex と POSIX」を参照してください。
 
 

関数 説明と実例 
yylex()  yylex() が実際のスキャン処理を行う関数です。これは、ファイル(デフォルトは stdin)を読み込み、パターン・マッチングを行い、パターンに関連付けされたアクションを実行します。デフォルトでは、入力の終端に達するまでマッチングを行い、終端に達したところでゼロを返します( return を使って、呼び出し側のプログラムに他の値を返すことは可能です。これは、***ページの 4.3 節「Flex と Bison」で説明されています)。したがって、インターフェイスを提供する簡単な方法の1つは、オプションの C コード領域の1つに以下のようなコードを追加することです。 
    #include <stdio.h> 

    int main(argc, argv) 
    int argc; 
    char **argv; 

      yylex(); 
しかしこのような場合には、Flex ライブラリ(-lfl)もしくは Lex ライブラリ(-ll)のいずれかをリンクして、そこからそのような main() 関数を取り込むことができます。この場合は、スキャナは単にファイルをスキャンしてルールに関連付けされたアクションを実行するだけであるという点に注意してください。 

yylex() の使い方としてもう1つよく見られるのが、マッチされたものが何であるかを示す値を返させることです。これはアクションに return 文を追加することで行われます。return 文を見つけると yylex() は指定された値を返します。これが、Bison によるパーサが Flex によるスキャナから情報を獲得する方法です。 

マッチされたテキストが何を表しているかを示すコードを返す return 文をルールが持っているのであれば、以下のようなインターフェイスを使うことができます。 

    #include <stdio.h> 

    int main(argc, argv) 
    int argc; 
    char **argv; 

      int return_code; 
      while((return_code = yylex()) != 0){ 
      switch(return_code){ 
        case KEYWORD1: 
          /* 何かを行う */ 
          break; 
        case KEYWORD2: 
          /* 何か別のことを行う */ 
          break; 
          ... 
        case KEYWORDn: 
yylex() のデフォルトの定義は int yylex(void) ですが、これは YY_DECL マクロを使うことによって変更することができます。例を示すと、以下のコードは yylex() の名前を gettok() に、型を char に対するポインタ型に変更し、パラメータ buffer を受け取るように指定します。 
    #undef  YY_DECL 
    #define  YY_DECL  char *gettok(char *buffer) 
注:ANSI 対応でない C を使っている場合は以下のように定義しなければなりません。  
    #define  YY_DECL  char *gettok(buffer) \ char *buffer; 
言葉を変えると、再宣言はターゲットとなる C コンパイラにとって正当な関数宣言でなければなりません。さらに、これらの再宣言は、ファイルの先頭にあるオプションの C コード領域になければならないという点に注意してください。  
 
yyin  yyin は、yylex() がそこから文字を読み込むファイルです。デフォルトは stdin ですが、fopen() を使って変更することができます。yyin を読み込むデフォルトの方法は、複数文字から成るブロックを一度に読むというものです。これは YY_INPUT マクロによって変更できます。YY_INPUT マクロは、ファイルではなく文字列をスキャンするためのスキャナを生成するのに便利です。YY_INPUT を定義する方法は以下のとおりです。 
    YY_INPUT(buffer,result,max_size) 
ここで buffer は入力バッファ、result は読み込まれた文字数がセットされる変数、max_size は buffer のサイズです。以下に、一度に1文字ずつ読み込むという入力方法に変更する再定義の例を示します。この方法を使うとかなり遅くなるので、お薦めはできません。 
    #undef  YY_INPUT 
    #define  YY_INPUT(buffer,result,max_size) \ 
        {\ 
        buffer[0] = getchar();\ 
        if(buffer[0] == EOF)\ 
          result = YY_NULL;\ 
        else\ 
          result = 1;\ 
        } 
注:これらの再宣言は、ファイルの先頭にあるオプションの C コード領域になければなりません。 
 
yyout  yyout はスキャナが ECHO の出力を書き込むファイルです。デフォルトは stdout ですが、これも fopen() を呼ぶことで変更できます。 
 
yytext  yytext は最後にマッチされた文字列、つまり最後に認識されたトークンを含む大域変数です。yytext の正しい外部定義は、Lex の場合の char の配列とは異なり、char に対するポインタ型である点に注意してください。つまり、yytext は 
    extern char yytext[]; 
ではなく、常に 
    extern char *yytext; 
のように宣言されなければならないということです。このようになっている理由は性能によるものです。yytext が配列であると、スキャナ中でそれを操作するコードは、コピー処理をたくさん行う必要があります。これに対して yytext がポインタである場合には、このようなことは必要ありません。 

通常は yytext は変更すべきではありません。yytext の内容が変更される必要がある場合には、代わりのバッファが使われるべきです(examples サブディレクトリの yymore2.lex ファイルでは yytext を直接操作する技法が実演されています。ただし、このようなやり方はお薦めできません)。 
 

yyleng  yyleng は最後に認識されたトークンの長さを保持する大域変数です。 
 
yywrap()  yywrap は yyin の終端に達した時に呼ばれる関数です。この関数が TRUE(非ゼロ)を返すとスキャナは終了し、FALSE(ゼロ)を返すと yyin が次の入力ファイルを指すように設定されたものと仮定してスキャン処理が続けられます。 

現在のところ yywrap() は、常に1を返すよう定義されているマクロです。そのため、これを再定義するには、まず最初にそれを #undef で定義解除しなければなりません。Lex では yywrap() は本物の関数です。Flex も将来のある時点でこれを関数として定義することになるでしょう。 
 

yymore() yymore() は、次に認識されるトークンで yytext の内容を更新するのではなく、その時点の yytext の内容のうしろにそのトークンを追加するよう Flex に通知する関数です。したがって、以下の例にたいして foobar という文字の並びを入力として与えると、stdout に foofoobar という文字の並びが書き込まれます。 
    %% 
    foo    ECHO;  yymore(); 
    bar    ECHO; 
これは、まず foo ルールによって foo という文字の並びが認識されて ECHO され、次に bar という文字の並びが認識されて yytext の内容のうしろに追加された後に foobar という文字の並びが ECHO されるからです。 

もう少し現実的な例を取り上げましょう。以下のコードは複数行の文字列を処理するのに yymore() を使っています。 

    /* 
    * yymore.lex: yymore() を有効に使う例 
    */ 

    %{ 
    #include <memory.h> 
    void yyerror(char *message) 

      printf("Error: %s\n",message); 

    %} 

    %x STRING 

    %% 
    \"   BEGIN(STRING); 

    <STRING>[^\\\n"]*  yymore(); 
    <STRING><<EOF>>  { 

          yyerror("EOF in string."); 
          BEGIN(INITIAL); 
                } 
    <STRING>\n  { 
          yyerror("Unterminated string."); 
          BEGIN(INITIAL); 
                } 
    <STRING>\\\n yymore(); /* 
                  * 複数行にわたる文字列を処理する 
                  */ 
    <STRING>\" { 
         yytext[yyleng-1] = '\0'; 
         printf("string = \"%s\"",yytext); 
         BEGIN(INITIAL); 
               } 
    %% 
この例ではエスケープ・シーケンスの変換がまったく行われていないので、文字列に対してさらに処理が必要である点に注意してください。この例は、***ページの 9.2 節「文字列リテラルの処理」において、エスエープ・シーケンスを処理する、より役に立つものに拡張されます。 
 
yyless(n yyless() は yymore() とほぼ反対のことを行うものです。この関数は、最初の n 文字以外のすべてを返します。返された文字の並びは、次のトークンをマッチするのに使われ、yyleng と yytext にはこの変化を反映した値が設定されます。yyless() を引数 n にゼロを指定して呼び出すと、全入力データが返され、スキャナは(BEGIN もしくはそれに類似のものでデフォルトの動作が変更されない限り)無限ループに入ります。例えば、次のコードに foobar という文字の並びを入力として与えると、foobarbar という文字の並びが出力されます。 
    %% 
    foobar    ECHO;  yyless(3); 
    [a-z]+    ECHO; 
これは、foobar が認識され ECHO された後に、bar が「戻される」からです。となると次にマッチするのは( [a-z]+ というルールでマッチされる)bar だけで、これが次に ECHO されることになります。 
 
input()  input() は yyin から次の文字を取って返す関数です。これは、標準的な Flex ルール・システムを使ったのではきれいに扱えない特定のケースを処理するのによく使われます。例えば、ほとんどの言語におけるコメントはこれを使って処理することができます。これを使う理由は、 
    %% 
    "/*".*"*/" 
が、ピリオドが改行以外の任意の文字にマッチするために、コメントが1行を超える場合にはうまくいかないし、一方、 
    %% 
    "/*"[.\n]*"*/" 
は、文字クラスが任意の文字にマッチしてしまうために、バッファをオーバーフローさせるか、さもなくばファイルの内容をすべて読み込んでしまうからです(実際には、排他的スタート状態を使うことで、このようなことを非常にエレガントな方法で処理することができます。実例については、***ページの 9 章「役に立つコードの抜粋」を参照してください。しかし、POSIX によりサポートされているにもかかわらず、ここで必要になるいくつかの機能を Lex が提供していないために、この方法には移植性がありません)。C のコメントは以下のようにして移植性のある方法で処理することができます。 
    %% 
    "/*" { 
      int a,b; 

      a = input(); 
      while(a != EOF){ 

        b = input(); 
        if(a == '*' && b == '/'){ 
          break; 
        }else{ 
          a = b; 

      if(a == EOF){ 
        error_message("EOF in comment"); 
注:スキャナが C++ コンパイラを使ってコンパイルされる場合は、この関数 input() は yyinput() という名前になります。これは input という名前が同一名の C++ ストリームと衝突するからです。また、Flex では input() は yytext の内容を破壊しますが、Lex では yytext は変更されずそのまま残ります。これは将来のリリースで修正される予定です。  
 
unput(c unput() は、文字 c が次にスキャンされる文字になるように、文字 c を入力ストリームに置く関数です。例えば、 
    %% 
    foo  unput('b'); 
は foo を b で置き換えます。これは、foo にマッチして b を戻し、この b が次にスキャンされる文字になるからです。デフォルトのルールにより、b は stdout に書き込まれます。 

1つの文字が次にスキャンされる文字になるということには1つ微妙な点があって、それは、文字列を入力ストリームに置きたい場合には逆順に行わなければならないということです。以下に例を示します。 

    foobar { 
      char *baz = "baz"; 
      int i = strlen(baz)-1; 

      while(i >= 0){ 

        unput(baz[i]); 
        i--; 
これは、foobar がマッチされた時に、入力ストリームに baz を置きます。以下は、してはならないことを示す例です。 
    /* 
    * unput.l : unput() を使って行ってはならない処理の例 
    */ 

    %{ 
    #include <stdio.h> 

    void putback_yytext(void); 
    %} 

    %% 
    foobar  putback_yytext(); 
    raboof  putback_yytext(); 
    %% 

    void putback_yytext(void) 

      int i; 
      int l = strlen(yytext); 
      char buffer[YY_BUF_SIZE]; 

      strcpy(buffer,yytext); 
      printf("Got: %s\n",yytext); 
      for(i=0; i<l; i++){ 

        unput(buffer[i]); 
この例に foobar を入力として与えると、まず foobar にマッチし、次に raboof にマッチする無限ループに陥ります。 

注:input() と同様に unput() も yytext の内容を破壊します。このことは、yytext から文字情報を返したい場合には、上の例に示されるように、まず yytext の内容をコピーしなければならないことを意味しています。  
 

yyterminate()  アクションの中で呼ばれると、yyterminate() はスキャナの実行を終了させ、その後に yylex() が 0 を返します。この後は、yyrestart()(下記参照)が呼ばれない限り、yylex() を呼んでもすぐに復帰してしまいます。 
 
yyrestart(file yyrestart() はスキャナの実行を再開するよう Flex に通知する関数です。これは引数を1つだけ取り、それはスキャンの対象となるファイル(通常は yyin)です。これは、EOF を処理するために使うこともできますし、また、Flex に割り込みをかけ、その後に再開始させることができるようにするために使うこともできます(Flex スキャナは再入可能ではないので、このようなことが必要になります)。 
 
YY_NEW_FILE  yyin が新しいファイルを指すよう変更され、処理が継続されるべきであるということを Flex に通知するマクロです。例を以下に示します。 
    /* 
    * cat.lex: YY_NEW_FILE の実演 
    */ 

    %{ 
    #include <stdio.h> 
    #define ERRORMESS "Unable to open %s\n" 

    char **names = NULL; 
    int current = 1; 
    %} 

    %% 
    <<EOF>> { 

        current += 1; 
        if(names[current] != NULL){ 
          yyin = fopen(names[current],"r"); 
          if(yyin == NULL){ 
            fprintf(stderr,ERRORMESS,names[current]); 
            yyterminate(); 

          YY_NEW_FILE; 
        } else { 
          yytereminate(); 
    %% 

    int main(int argc, char **argv) 

      if(argc < 2){ 
        fprintf(stderr,"Usage: cat files....\n"); 
        exit(1); 

      names = argv; 

      yyin = fopen(names[current],"r"); 
      if(yyin == NULL){ 

        fprintf(stderr,ERRORMESS,names[current]); 
        yyterminate(); 

      yylex(); 
ECHO  yytext の内容を yyout に書き込むマクロです。 
 
REJECT  REJECT は、その時点においてマッチしているものを受け入れないで、次に最もよくマッチするものを受け入れるようスキャナに通知するマクロです。スキャナはマッチするものの中で最長のものを探し、マッチするものが2つあってその長さが同じ場合は、記述ファイルにおいて最初に定義されている方を選択します。このことは、認識されるテキストの長さは、同一の長さになることもあり、あるいはより短かくなることもあるということを意味しています。REJECT を使った後は、yytext と yyleng は新しい値を取ります。REJECT に関して知っておくべき重要な点が2つあります。1つめは、REJECT は分岐命令であり、決っして戻ってこないので、REJECT のうしろに記述されたアクションは実行されないということです。2つめは、REJECT とファスト・テーブル(fast tables)(-F)は一緒に使うことはできないということです。以下に簡単な例を示します。 
    /* 
    * reject.lex: REJECT と unput() を悪用する実例 
    */ 

    %% 
    UNIX { 

        unput('U'); unput('N'); 
        unput('G'); unput('\0'); 
        REJECT; 
    GNU printf("GNU is Not Unix!\n"); 
    %% 
この例は、新式のテキスト代替の技法を示しています。UNIX にマッチするものが見つかると、unput() によって GNU という文字の並びが戻され、これによりその時点におけるスキャン・バッファの内容が上書きされます。次に REJECT により分岐が行われ、別のものにマッチするようスキャナに対して通知が行われます。GNU がバッファに書き込まれたので、これが次にマッチされ、そのアクションが実行されます。以下に、その結果こうなるであろうと思われる例を示します。 
    UNIX return  
    GNU is Not Unix! 
実際のところは、Flex において REJECT の用途はほんの少ししかありません。上記以外では、重複するパターンや状態の変更に使うことができます。例を示すと、以下のようになります。 
    nday         [1-9]|[1-2][0-9]|3[0-1] 
    nmonth     [1-9]|1[0-2] 
    nyear        [0-9]{1,4} 

    %x DAY MONTH YEAR 
    %% 

    {nday}                 BEGIN(DAY); REJECT; 
    <DAY>{nday} 

      ... 
    {nmonth}             BEGIN(MONTH); REJECT; 
    <MONTH>{nday} 
      ... 
    {nyear}                BEGIN(YEAR); REJECT; 
    <YEAR>{nday} 
      ... 
この例では、日付の形式は重複しており、最初に認識された構成要素によって、どのように日付をパースするかを決定します。しかし、この例は少々不自然な感じがします。というのは、少し考えれば REJECT を使わずに、より効率的なスキャナにすることができるからです。これは、***ページの 3.8.4 節「スタート状態を使う実例」において示されています。 
 
BEGIN  BEGIN はスキャナをある特定のスタート状態にするためのマクロです。BEGIN に続く名前はスタート状態の名前です。例えば、 
    %x FLOAT 
    %% 
    float     BEGIN(FLOAT) 
    <FLOAT>some_rule     some_action 
    ... 
は、float という単語がマッチした時に、スタート状態を FLOAT に設定します(詳細については 3.8.1 節「スタート状態の説明」を参照)。 
 
YY_USER_ACTION  YY_USER_ACTION はルール・セクション中のどのアクションよりも前に実行されるアクションを定義するマクロです。これは、以下の例で示すように、yytext の内容の小文字から大文字への変換等を行うのに役に立ちます。 
    /* 
    * user_act.lex: YY_USER_ACTION を使うユーザ・アクションの例 
    */ 

    %{ 

    #include <ctype.h> 

    void user_action(void); 

    #define YY_USER_ACTION user_action(); 

    %} 

    %% 

    .*      ECHO; 
    \n     ECHO; 

    %% 

    /* 
    * このユーザ・アクションはすべての文字を単に大文字に変換する 
    */ 

    void user_action(void) 

      int loop; 

      for(loop=0; loop<yyleng; loop++){ 

        if(islower(yytext[loop])){ 
          yytext[loop] = toupper(yytext[loop]); 
これは、すべての入力文字を単に大文字に変換して ECHO します。YY_USER_ACTION のデフォルトの設定では何も実行されません。 
 
YY_USER_INIT  YY_USER_INIT は、スキャン処理が開始される前に実行されるアクションを定義するマクロです。これは基本的には main() 関数の中で yylex() を呼び出す文の前に同様のコードを記述するのと同じことです。以下に簡単な例を示します。 
    /* 
    * userinit.lex: YY_USER_INIT を使う例 
    */ 

    %{ 
    #define YY_USER_INIT open_input_file() 

    extern FILE *yyin; 

    void open_input_file(void) 

      char *file_name,buffer[1024]; 

      yyin = NULL; 

      while(yyin == NULL){ 

        printf("Input file: "); 
        file_name = fgets(buffer,1024,stdin); 
        if(file_name){ 
          file_name[strlen(file_name)-1] = '\0'; 
          yyin = fopen(file_name,"r"); 
          if(yyin == NULL){ 
            printf("Unable to open \"%s\"\n", 
            file_name); 
        } else { 
          printf("stdin\n"); 
          yyin = stdin; 
          break; 

    %} 
    %% 

これは、ファイルがオープンされるか EOF が検出されるまで、ユーザに入力ファイル名を入力するよう催促します。EOF が検出された場合は、入力元はデフォルトで stdin になります。これは、以下と同じことです。 
    /* 
    * この例は前の例と同じことを YY_USER_INIT を使わずに行う 
    */ 

    %{ 
    void open_input_file(void) 

      char *file_name,buffer[1024]; 

      yyin = NULL: 

      while(yyin == NULL){ 

        printf("Input file: "); 
        file_name = fgets(buffer,1024,stdin); 
        if(file_name){ 
          file_name[strlen(file_name)-1] = '\0'; 
          yyin = fopen(file_name,"r"); 
          if(yyin == NULL){ 
            printf("Unable to open \"%s\"\n", 
            file_name); 
        } else { 
          printf("stdin\n"); 
          yyin = stdin; 
          break; 

    %} 
    %% 
    %% 

    int main(int argc, char *argv[]) 

      open_input_file(); 
      yylex(); 
YY_BREAK  YY_BREAK はマクロです。これは、インターフェイス的な機能ではなく、むしろ生成されるコードを変更するために使うことができるものです。 

スキャナ中においてすべてのアクションは1つの大きな switch 文の要素であり、それらはデフォルトで C の break; 文に置き換えられる YY_BREAK によって区切られます。ルールのアクション部が多くの return 文を含んでいる場合、コンパイラが statement not reached というエラー・メッセージをたくさん出力するかもしれません。YY_BREAK を再定義することによって、この警告メッセージの出力を止めることが可能です。再定義は、セミ・コロンを含む正当な C の文でなければなりません。 

注:YY_BREAK を再定義して空にするのであれば、アクションの最後は必ず return; か break; になるようにしてください。 

 
4.2 Flex と C の簡単な実例

ある単語が現れた時にそれを別の単語に置き換える必要のあることがよくあります。例えば、ある名前が現れるたびにそれをある1つの環境変数の値で置き換えてくれるユーティリティを作りたいとしましょう。そして、以下のようなことができるように、そのユーティリティがフィルターとして動作するようにさせたいとします。

以下にこのようなことを実現する方法を示す Flex ファイルの簡単な例を示します。 このソース・ファイルは examples サブ・ディレクトリにあり、その名前は myname.lex です。これを構築するには examples サブ・ディレクトリに移動して make myname を実行するか、あるいは、以下を実行します。 ここで -lfl はリンカに対して Flex ライブラリをリンクするよう通知します。現在のところ Flex ライブラリにはデフォルトの main() 関数だけが含まれています。将来のバージョンの Flex では、他の関数も含まれるようになるでしょう。Flex ライブラリがインストールされていない場合は、この部分は -ll でなければなりません。

いずれの場合でも、最終的には myname という名前の実行ファイルが生成されるはずです。これは、以下のような変換処理を実行するフィルタです。
 

%NAME  ユーザのログイン名に置き換えられます。 
 
%HOST  ユーザのホスト・コンピュータ名に置き換えられます。 
 
%HOSTTYPE  ユーザのホスト・コンピュータのマシン・タイプに置き換えられます。 
 
%HOME  ユーザのホーム・ディレクトリを表すパスに置き換えられます。 
 
したがって、以下のような内容を持つファイル myname.txt を作成して、 以下を実行すると、 以下のテキストに似たものが stdout へ書き込まれます。 このプログラムがうまく動作するのは、yyint と yyout がデフォルトでは stdin、stdout にそれぞれ割り当てられ、かつ、デフォルトのアクションが yyin の内容を yyout にコピーするからです。また、個々のルールに対応する唯一のアクションが単一行で記述されているため、{} は必要ではないことに注意してください。このような場合には、アクションを {} で囲むか否かは、個人的な好みの問題になります。

これが、引用符で囲まれた部分にあるものも含めて、指定された名前が現れるところすべてにマッチしたことに気がつきましたか? Flex においては、引用符で囲まれた部分にあるものにマッチさせたくない場合には、それに対応するルールを作成することにより、そうしないよう明示的に Flex に通知しなければなりません。以下に例を示します。

この例では排他的スタート状態を使って、文字列中のテキストが変更されることのないようにしています。この例も examples サブ・ディレクトリにあるもので、その名前は myname2.lex です。

4.3 Flex と Bison

Bison は、Flex と同様、ある記述情報を受け取って、それをもとに C のコードを生成するプログラムです。両者の違いは、Bison が C や Pascal のような言語の文法に関する記述情報を入力として受け取り、その記述情報からパーサを生成する点にあります。Flex と Bison を結合することにより、言語の字句解析と構文解析の両方を処理することができるようになります(これらはコンパイラ・デザインにおいて最も容易に自動化できる部分です)。

生成されるパーサが機能するためには、Bison は yylex() という関数を必要とします。この関数はユーザによって提供され、呼び出された時に、パースされている言語のある要素を表わす整数値を Bison に返します。Flex においてスキャン処理を行うルーチンは yylex() であり、これはデフォルトでは整数値を返します。これにより、Flex と Bison を一緒に使うのは非常に簡単になります。

注:以下の節では、読者が Bison の基本的なパーサの宣言を理解しているものと仮定します。Bison を使った経験のない人には、パーサの定義は混乱をもたらす可能性がありますので、先に進む前に是非 Bison マニュアルを読んでください。Bison に興味の無い人は、この節全体をスキップしても構いません。

4.3.1 Flex と Bison のインターフェイス

Flex と Bison の間で情報を渡す基本的な方法は、関数 yylex() を使うことです。これは、Flex により生成されるスキャナにおいてスキャン処理を実行する関数の名前です。Flex の入力ファイルのアクション部分において return 文を使うことによって、単なる 0 や 1 以外の値を返すことができます。これにより、最後に認識されたトークンを表わす整数値を yylex() は返すことができます。

Bison を -d オプション付きで使うと、Bison は .tab.h という拡張子を持つファイルを生成します。このファイルには、記述情報中にある正当なトークンの1つ1つに対する一意な定義情報が含まれます。この出力情報は、特にスキャナによって使用されることを想定して設計されています。このファイルを Flex により生成されたスキャナに含めることで、2つのプログラムの間に非常に明確なインターフェイスを生成することができます。例として、以下に Bison のファイルを示します。このファイルの名前を expr.y としましょう。

これは非常に簡単な計算機の文法定義です。

-y -d オプション付きで呼び出されると、Bison は y.tab.h というファイルを生成します。このファイルには以下のような定義か、それに極めてよく似た定義が含まれます。

Flex がトークンの値を正しく Bison に返すことができるように、(#include を使って)これをスキャナに含めることができます。そのコードは以下のようなものになります。 これらのファイルは以下のようにしてコンパイルすることができます。 また、この例のソースが手元にあれば、examples サブ・ディレクトリにおいて make expr を実行するだけでコンパイルできます。どちらの方法によっても expr という名前の簡単な計算機が生成されます。これは以下のような表現式をパースして、その結果を出力します。 これを見てお分かりのように、この種のインターフェイスは非常に柔軟であり、かつ、保守も非常に容易です。(トークンを定義する名前が変わらない限り)Bison と Flex の間のインターフェイスを変更することなく、Flex の入力情報、Bison の入力情報において機能の追加や削除、定義やコードの変更を行うことが可能です。

この例では、Flex と Bison の間で情報を渡すための別の方法を導入していることに注意してください。この例では、数字の値を Bison に返すのに yylval を使っています。これについては次の節でより詳細に説明します。ここではとりあえず、return 文の使い方を学んでおいてください。

注:これは単純な例です。表現式のパース処理についてより詳しく知りたい人は、Bison マニュアルを参照してください。

4.3.2 YYSTYPE と yylval

Flex から Bison に対して、単なる整数値以上の情報を渡す必要のあることがよくあります。例えば、コンパイラにおいては、どのようなトークンが認識されたかだけではなく、そのトークンの値についても知る必要のある場合がときどきあります。文字列、文字、数値定数などがこの良い例です。ここで問題なのは、どのようにして Flex にこうした情報を返させるかです。

その答えは Bison が持っている %union 文で、これは YYSTYPE という型を定義するものです。YYSTYPE は、パーサ定義中において使われるすべての正当なデータ型の共用体です。Bison がカレントなパース状態に関連づけたデータを保存するために使う、YYSTYPE 型の変数 yylval というものがあります。Flex から yylval に値を設定することができるので、トークンの型だけでなく、それ以上の情報を Bison に返すことができます。

Bison において %union を宣言して -d オプションを使うと、Bison は .tab.h という拡張子を持つファイルを作成して、そこにトークンの定義情報だけでなく、YYSTYPE と yylval の宣言も含めます。したがって yylval にアクセスするためにしなければならないことは、Flex の定義情報の中にこの .tab.h ファイルをインクルードすることだけです。これは、追加の C コード・セクションにおける定義の先頭でインクルードしなければなりません(***ページの 4.3.1 節「Flex と Bison のインターフェイス」を参照)。

注:初期のバージョンの Bison は自動的に YYSTYPE と yylval の宣言を生成しません。この場合には、より新しいバージョンの Bison を入手するか、もしくは、Flex の定義ファイルの先頭において YYSTYPE と yylval を宣言する必要があります。

4.4 Flex と Bison のもう1つの実例

コードを読むのはプログラミングの方法を学ぶ良い方法です。そこで、Flex、Bison のインターフェイス例をもう1つ示すことにします。下の例では、拡張してデータベースを操作するために使うことができるような小規模な言語のための簡単なパーサを構築します。

4.4.1 インターフェイス言語

データベースとのインターフェイス言語は英語の非常に小さなサブセットになります。文法はおおよそ以下のようになります。

結果として作成されるプログラムは以下のような文章を受け付けます。 この例では、Bison と Flex の間のインターフェイスが明確に示されるよう、文章の簡単な解析結果が表示されます。このプログラムを試しに実行してみると、その表示結果は大体以下のようになります。 これは特別便利なものではありません。というのは、これは文章の構成要素を表示する以外に何も行わないからです。しかし、そこには拡張のためのフックもありますし、一般的な技法も示されています。より一般的な形式の文章を受け付けるよう、この例を拡張してみてください。ほとんどの場合、文章は動詞句と名詞句に分割することができますが、所有格名詞、名詞のうしろに名詞が続く場合など、文章を構成する他の要素も許容されるようにする必要があります( FIND ALL JONE'S CAT NAMES. のような文章をどうやってパースするかを想像してみてください)。Bison の文法やその使い方に関する詳しい説明については Bison マニュアルを参照してください。

4.4.2 実装:コマンド文パーサ

上の節で、小規模な言語について説明しました。次にそれを実装してみることにしましょう。以下のファイルがこれを実現します。

注:これはあくまでも1つの例として見てください。特に文法の部分は、英語のパース処理としてはあまり良い例ではありません。

以下は Bison のファイルです。%union の部分、および、yylval にアクセスするために $$ と $n を使う方法に注目してください。

以下は Flex のファイルです。文字列が渡される方法に注意してください。これは最適化された方法ではありませんが、最も理解しやすい方法です。 これらのファイルは以下を実行することでコンパイルできます。 また、この例のソースが手元にあれば、examples サブ・ディレクトリにおいて make front を実行することでもコンパイルできます。

注:Bison パーサは alloca.c というファイルを必要とします。このファイルは examples サブ・ディレクトリにあります。Bison の代わりに yacc を使うのであれば、このファイルは必要ありません。

4.4.3 実装に関する注

以下に実装に関する注を示します。


Copyright (C) 1992, 1993 Free Software Foundation

Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.

Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.

Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the Free Software Foundation.


日本語訳:市川和久
Japanese translation by Kazuhisa Ichikawa (ki@home.email.ne.jp)